\section{【实现】bootloader加载并运行ucore}\label{ux5b9eux73b0bootloaderux52a0ux8f7dux5e76ux8fd0ux884cucore}

了解完proj2/3的组成与编译，并大致理解上述两个背景知识后，我们就可以分析bootloader加载并运行ucore操作系统的工作流程。

硬盘数据是储存到硬盘扇区中，一个扇区大小为512字节。读一个扇区的流程可参看bootmain.c中的readsect函数实现。大致如下：

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  读I/O地址0x1f7，等待磁盘准备好；
\item
  写I/O地址0x1f2\textasciitilde{}0x1f5,0x1f7，发出读取第offseet个扇区处的磁盘数据的命令；
\item
  读I/O地址0x1f7，等待磁盘准备好;
\item
  连续读I/O地址0x1f0，把磁盘扇区数据读到指定内存。
\end{enumerate}

这个函数是被bootloader用于读取硬盘上的ucore操作系统。bootloader为了读取硬盘上的ucore操作系统，将调用bootmain函数首先读取了位于主引导扇区的后的连续8个扇区（可参见bootmain函数中的第一条语句），并把数据放到0x10000处（可回顾一下2.7.1中描述链接bin/kernel的过程），并按照数据结构elfhdr来解析这块4KB大小的数据；如果其e\_magic数据域不等于ELF\_MAGIC（即0x464C457F），则表示这个不是标准的ELF格式的文件；如果等于ELF\_MAGIC，则继续解析，并根据其e\_phnum数据域的值来读取多个program
header，并根据program
header的信息，了解到ucore中各个segment的起始位置和大小，然后把放在硬盘上的相关segment读入到内存中。

\textbf{【实验】分析kernel并在bootloader中显示kernel的segment信息}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  在proj3目录下执行命令make，则会在bin目录下生成kernel，即ELF执行格式文件的操作系统ucore；
\item
  在proj3目录下执行命令 readelf -h bin/kernel，可得到有关elf
  header的如下信息

\begin{lstlisting}
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x100000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          19872 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         17
  Section header string table index: 14
\end{lstlisting}

  从中，我们可以看到kernel的入口点在0x100000，program
  header相对文件的偏移位置在52，elf header的大小为52字节，program
  header的大小为32字节。
\item
  在proj3目录下执行命令 readelf -l bin/kernel，可得到有关program
  header的如下信息 Elf file type is EXEC (Executable file) Entry point
  0x100000 There are 3 program headers, starting at offset 52

\begin{lstlisting}
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00100000 0x00100000 0x01038 0x01038 R E 0x1000
  LOAD           0x002038 0x00102038 0x00102038 0x00004 0x00004 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata 
   01     .data 
   02     
\end{lstlisting}
\end{enumerate}

从中，我们可以看到kernel的入口点在0x100000，代码段位于0x100000，大小为0x1038；数据段位于0x102038，大小为0x04。

\textbf{【实验】用gdb调试bootloader，并在gdb中显示kernel的segment信息}

我们还可通过用gdb调试bootloader进行验证，具体步骤如下： 5.
开两个窗口；在一个窗口中，在proj3目录下执行命令make； 6.
在proj3目录下执行 ``qemu -hda bin/ucore.img -S
--s'',这时会启动一个qemu窗口界面，处于暂停状态，等待gdb链接； 7.
在另外一个窗口中，在proj3目录下执行命令 gdb obj/bootblock.o； 8.
在gdb的提示符下执行如下命令，会有一定的输出： (gdb) target remote :1234
\#与qemu建立远程链接 (gdb) break bootmain.c:100
\#在bootmain.c的第100行设置一个断点 (gdb) continue \#让qemu继续执行\\
这时qemu会继续执行，但执行到bootmain.c的第100行时会暂停，等待gdb的控制。这时可以在gdb中继续输入如下命令来参考kernel的信息：
(gdb) p /x \emph{(struct elfhdr })0x10000 \#按struct
elfhdr结构显示0x10000处内容 \$7 = \{e\_magic = 0x464c457f, e\_elf =
\{0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0\}, e\_type
= 0x2, e\_machine = 0x3, e\_version = 0x1, e\_entry = 0x100000, e\_phoff
= 0x34, e\_shoff = 0x4550, e\_flags = 0x0, e\_ehsize = 0x34,
e\_phentsize = 0x20, e\_phnum = 0x3, e\_shentsize = 0x28, e\_shnum =
0x11, e\_shstrndx = 0xe\}
查看bootmain函数，可以知道，此时在0x10000处已经读入了kernel的ELF头信息，有三个program
header 表(e\_phnum值),继续在gdb中敲入命令，可以得到更多信息： (gdb) next
\#执行下一条指令 (gdb) p /x \emph{ph \#获得text段的program header表信息
\$5 = \{p\_type = 0x1, p\_offset = 0x1000, p\_va = 0x100000, p\_pa =
0x100000, p\_filesz = 0x1038, p\_memsz = 0x1038, p\_flags = 0x5,
p\_align = 0x1000\} (gdb) next \#执行下一条指令 (gdb) next
\#执行下一条指令 (gdb) p /x }ph \#获得data段的program header表信息 \$6 =
\{p\_type = 0x1, p\_offset = 0x2038, p\_va = 0x102038, p\_pa = 0x102038,
p\_filesz = 0x4, p\_memsz = 0x4, p\_flags = 0x6, p\_align = 0x1000\}

\begin{lstlisting}
对照readelf命令输出的信息，可以发现bootloader正确读出了text段和data段的program header表信息，并根据这些信息调用如下函数
    -->readseg(ph->p_va, ph->p_memsz, ph->p_offset);
        -->readsect((uint8_t *)va, offset);
\end{lstlisting}

把这两个段的内容读入到正确的线性内存地址中。然后再根据e\_entry =
0x100000，跳转到0x100000处去执行，这其实就是把处理器控制权转移给了ucore了。
